#include <jni.h>
#include <string>
// ndk提供的日志库
#include <android/log.h>

#include <pthread.h>

// 定义打印日志的宏，__VA_ARGS__即代表可变参数...
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, "JNI", __VA_ARGS__)

// jniLibs中的libTest.so声明的test方法,使用c编译，在CPP中使用
extern "C" {
extern int test();
}

// 需要被动态注册的jni方法
void dynamicTest() {
    LOGI("dynamic void");
}

// 可以不写env，thiz参数，但是可能造成参数混乱，以为num是env
jint dynamicTest2(JNIEnv *env, jobject thiz, jint num) {
    LOGI("dynamic2 int");
    return 100 + num;
}

// 调用jni前，第一个被调用的函数，用于初始化，可以进行进行动态注册
JavaVM *_vm;

// native方法与cpp方法映射的集合
static const JNINativeMethod method[] = {
        // java方法名 方法签名 jni方法
        {"testJavaDynamic",  "()V",  (void *) dynamicTest},
        {"testJavaDynamic2", "(I)I", (int *) dynamicTest2},
};

// 声明需要动态注册的类
static const char *mClassName = "com/fauks/ndk/NDKBasicActivity";

/*int JNI_OnLoad(JavaVM *vm, void *r) {
    _vm = vm;
    // 获取JNIEnv，jvm传入
    JNIEnv *env = nullptr;
    // 小于0失败，=0成功
    int result = vm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (result != JNI_OK) {
        // 直接返回失败
        return -1;
    }

    jclass jcls = env->FindClass(mClassName);

    // 动态注册2个方法
    env->RegisterNatives(jcls, method, sizeof(method) / sizeof(JNINativeMethod));
    return JNI_VERSION_1_6;
}*/



// 静态注册，cpp的方法名必须与java类名/方法名 匹配
extern "C"
JNIEXPORT void JNICALL
Java_com_fauks_ndk_NDKBasicActivity_ndkParams(JNIEnv *env, jobject thiz, jintArray nums_,
                                              jobjectArray hobbys_) {
    // 指向数组首元素地址，通过传递进来的参数获取其指针
    jint *nums = env->GetIntArrayElements(nums_, NULL);
    // C的调用方式,env是个二级指针，C需要解一次指针
    //jint *munsc = (*env)->GetIntArrayElements(env, nums_, NULL);

    // 获取数组长度
    int32_t length = env->GetArrayLength(nums_);
    for (int i = 0; i < length; ++i) {
        //*(nums+i) 解引用，指针转换为数据
        //__android_log_print(ANDROID_LOG_INFO, "JNI", "intArray:%d", *(nums + i));
        LOGI("intArray:%d", *(nums + i));
    }

    // 释放资源，mode参数，刷新Java数组，即在C/C++中修改相关的值，Java层是否修改，如果传入2 Java层不修改
    // 0:刷新java数组，并释放C++数组
    // 1=JNI_COMMIT:只刷新Java数组，不释放
    // 2=JNI_ABORT: 不刷新，只释放
    env->ReleaseIntArrayElements(nums_, nums, 0);
    // 打印字符串数组
    jint strLength = env->GetArrayLength(hobbys_);
    for (int i = 0; i < strLength; ++i) {
        // 将obj强转为string
        jstring str = static_cast<jstring>(env->GetObjectArrayElement(hobbys_, i));
        // 转换为C/C++的字符串
        const char *s = env->GetStringUTFChars(str, 0);
        LOGI("strArray:%s", s);
        env->ReleaseStringUTFChars(str, s);
    }
}

// 全局引用
jobject petBean;

// 如果多个c++文件都声明了test方法，编译时会产生符号重复异常
// 使用extern引入其他地方的test方法可以直接调用，表示某个文件中有test方法
// extern void test();

extern "C"
JNIEXPORT void JNICALL
Java_com_fauks_ndk_NDKBasicActivity_ndkParamsClass(JNIEnv *env, jobject thiz, jobject user_,
                                                   jstring name_) {
    const char *name = env->GetStringUTFChars(name_, nullptr);
    // 通过反射调用Java方法
    jclass beanCls = env->GetObjectClass(user_);
    // 使用方法签名找到要调用的方法，使用javap命令获取
    jmethodID getAge = env->GetMethodID(beanCls, "getAge", "()I");
    // 调用
    jint age = env->CallIntMethod(user_, getAge);
    LOGI("C++ call user getAge:%d", age);

    // set方法
    jmethodID setAge = env->GetMethodID(beanCls, "setAge", "(I)V");
    env->CallVoidMethod(user_, setAge, 100);

    // 调用静态方法
    jmethodID printInfo = env->GetStaticMethodID(beanCls, "printInfo", "(Ljava/lang/String;)V");
    // 创建java字符串
    jstring jmsg = env->NewStringUTF("c++ call static method");
    // 静态方法需要使用class 调用
    env->CallStaticVoidMethod(beanCls, printInfo, jmsg);

    // 参数是自定义的object
    jmethodID printPet = env->GetStaticMethodID(beanCls, "printPet",
                                                "(Lcom/fauks/ndk/model/Pet;)V");

    // 在jni中创建对象
    jclass petClass = env->FindClass("com/fauks/ndk/model/Pet");
    jmethodID constuct = env->GetMethodID(petClass, "<init>", "(I)V");
    jobject pet = env->NewObject(petClass, constuct, 19);

    env->CallStaticVoidMethod(beanCls, printPet, pet);
    // 使用pet对象，主动调用可以让对象立刻回收节省内存
    env->DeleteLocalRef(pet);

    // 获取属性
    jfieldID ageField = env->GetFieldID(beanCls, "age", "I");
    env->SetIntField(user_, ageField, 1100);

    // 释放局部应用
    env->DeleteLocalRef(jmsg);

    env->ReleaseStringUTFChars(name_, name);
}

// 定义结构体，用于传参。结构体的属性默认都是public包括继承结构体，类默认为private，继承也是私有继承
struct Context {
    jobject instance;
};

// 创建线程
void *threadTask(void *args) {
    JNIEnv *env;
    // native线程 附加到 java 虚拟机
    // 构建属于当前线程的jni环境
    jint result = _vm->AttachCurrentThread(&env, nullptr);
    if (result != JNI_OK) {
        return nullptr;
    }

    Context *context = static_cast<Context *>(args);
    // 获取MainActivity
    jclass cls = env->GetObjectClass(context->instance);
    // 获取方法
    jmethodID method = env->GetMethodID(cls, "updateUI", "()V");
    env->CallVoidMethod(context->instance, method);

    // 释放资源
    delete (context);
    context = 0;

    env->DeleteGlobalRef(context->instance);
    _vm->DetachCurrentThread();
    return nullptr;
}

// JNIEnv和线程有关，不能跨线程
extern "C"
JNIEXPORT void JNICALL
Java_com_fauks_ndk_NDKBasicActivity_testThread(JNIEnv *env, jobject thiz) {
    pthread_t pid;
    // 启动线程，该方法在java的main thread传入，不能在传递给threadTask其他线程,应该从全局的JavaVM中获取
    // pthread_create(&pid, nullptr, threadTask, env);,此处的env不能在其他线程使用
    auto *context = new Context;
    context->instance = env->NewGlobalRef(thiz);
    pthread_create(&pid, nullptr, threadTask, context);
}

// 使用交叉编译的C库
/*extern "C"
JNIEXPORT void JNICALL
Java_com_fauks_ndk_MainActivity_testBuild(JNIEnv *env, jobject thiz) {
    LOGI("call libTest.so method %d", test());
}*/

// 使用cmake
extern "C"
JNIEXPORT jint JNICALL
Java_com_fauks_ndk_NDKBasicActivity_testCmake(JNIEnv *env, jobject thiz) {
    return test();
}